filter Get-CGPOReport
#region Get-CGPOReport
{
	<#
	#region CommentBasedHelp  
	  .SYNOPSIS
	    Given an extension object from Get-CGPOExtension, returns the specifics of 
	    the group policy object that contains the extension.  Currently just a
	    utility function to add the data.
	  
	  .PARAMETER  CGPOExtensionData
	    A 90% created CGPOExtensionInformation object.
	#>
	#endregion
  #region cmdletbinding
  [CmdletBinding()]
  #endregion
	#region parameters
  param
  (
    [parameter(
    	Mandatory=$false,
      ValueFromPipeline=$true
      )]
    [Xml.XmlDocument]
    $gpoReport,
		[parameter(
			Mandatory=$false
		)]
		[System.String]
    $reportPath,
    [parameter(
    	Mandatory=$false
    	)]
    [System.String[]]
    $IncludedExtensionDetails = "None",
    [parameter(
    	Mandatory=$false
    	)]
    [switch]
    $IncludeReportsWithNoExtensionDetails,
    [parameter(
      Mandatory=$false
      )]
    [Xml.XmlNamespaceManager]
    $namespaceMgr,
    [parameter(
    	Mandatory=$false
    	)]
    [switch]
    $summarize
    #TODO: Implement summarize switch for get-cgporeport
	)
	#endregion
	# Determine where our GPOReports are going to be coming from, is the $reportPath
	# a single file, or a directory containing multiple files?  Only works if $gpoReport
	# is not supplied...perhaps using parameter sets would be a better way??
	if ($gpoReport -eq $null)
	{
		#init counter
		$i = 0
		if (Test-Path $reportPath)
		{
			if ( (Get-Item $reportPath).PSIsContainer )
			{
				$reports = Get-ChildItem -Path $reportPath -Filter *.xml
			}
			else
			{
				$reports = Get-Item $reportPath
				Write-Debug $reports
			}
		}
		foreach ($r in $reports)
		{
			$gpoReport = New-Object System.Xml.XmlDocument
			$gpoReport.Load($r.fullname)
			# And for our next trick, we recurse into this filter, feeding in our parameters
			# include the fullname of the report that we have loaded into the XMLDocument to track which file
			# the information is in.
			# First we'll build the command up
			$cmd = 'Get-CGPOReport -gpoReport $gpoReport -namespaceMgr $namespaceMgr -reportPath $r.fullname -IncludedExtensionDetails $IncludedExtensionDetails'
			if ($IncludeReportsWithNoExtensionDetails) { $cmd += ' -IncludeReportsWithNoExtensionDetails' }
			if ($summarize) { $cmd += ' -summarize' }
			# Invoke the command string that we have built
			$output = Invoke-Expression $cmd
			Write-Output $output
			$i = $i + 1
			Write-Progress -Activity "Generating CGPOReport Objects from $reportPath" -Status "% Complete" -PercentComplete ($i/$($reports.count)*100)
		}
	}
	else
	{
		if (-not $namespaceMgr)
		{
			# Create a namespace manager from our navigator object's nametable
			$namespaceMgr = New-Object System.Xml.XmlNamespaceManager $gpoReport.CreateNavigator().NameTable
			$namespaces = $gpoReport.CreateNavigator().GetNamespacesInScope('All')
			foreach ($key in $namespaces.keys) { $namespaceMgr.AddNamespace( $key, $namespaces.$key ) }
			$namespaceMgr.AddNamespace(	"e", "http://www.microsoft.com/GroupPolicy/Settings" )
			# Add the additional default prefix used for the GPO Identifier element in the XML
			$namespaceMgr.AddNamespace( "id" , "http://www.microsoft.com/GroupPolicy/Types" )
			# Add another default prefix used in the Security Descriptor section of the reports
			$namespaceMgr.AddNamespace( "sd" , "http://www.microsoft.com/GroupPolicy/Types/Security" )
		}
		# Obtain the root GPO node
		$GPONode = $gpoReport.SelectSingleNode("./e:GPO",$namespaceMgr)
		$nav = $GPONode.CreateNavigator()
		$GPODetails = New-Object PSObject -Property @{
			fullReport							= $GPONode
			ReportPath							= $reportPath
			NamespaceMgr						= $namespaceMgr
			Identifier							= $GPONode.SelectSingleNode("./e:Identifier/id:Identifier", $namespaceMgr).InnerXml
			Domain									= $GPONode.SelectSingleNode("./e:Identifier/id:Domain", $namespaceMgr).InnerXml
			Name										= $GPONode.SelectSingleNode("./e:Name", $namespaceMgr).InnerXml
			IncludeComments					= $GPONode.SelectSingleNode("./e:IncludeComments", $namespaceMgr).InnerXml.toLower() -eq "true"
			Created									= [System.DateTime]$GPONode.SelectSingleNode("./e:CreatedTime", $namespaceMgr).InnerXml
			Modified								= [System.DateTime]$GPONode.SelectSingleNode("./e:ModifiedTime", $namespaceMgr).InnerXml
			Read										= [System.DateTime]$GPONode.SelectSingleNode("./e:ReadTime", $namespaceMgr).InnerXml
			SecurityDescriptor			= Get-CGPOSecurityDescriptor -namespaceMgr $namespaceMgr -sdNode $GPONode.SelectSingleNode("./e:SecurityDescriptor", $namespaceMgr)
			WMIFilterDataAvailable	= $GPONode.SelectSingleNode("./e:FilterDataAvailable", $namespaceMgr).InnerXml.toLower() -eq "true"
			WMIFilterName						= $GPONode.SelectSingleNode("./e:FilterName", $namespaceMgr).InnerXml
			ComputerEnabled					= $GPONode.SelectSingleNode("./e:Computer/e:Enabled", $namespaceMgr).InnerXml.toLower() -eq "true"
			UserEnabled							= $GPONode.SelectSingleNode("./e:User/e:Enabled", $namespaceMgr).InnerXml.toLower() -eq "true"
			ComputerExtensions			= $GPONode.SelectNodes("./e:Computer/e:ExtensionData/e:Name",$namespaceMgr) | ForEach-Object { $_.InnerXml }
			UserExtensions					= $GPONode.SelectNodes("./e:User/e:ExtensionData/e:Name",$namespaceMgr) | ForEach-Object { $_.InnerXml }
			Links										= `
				&{
					[PSObject[]]$arrLinks = @()
					foreach ($l in $GPONode.SelectNodes("./e:LinksTo",$namespaceMgr))
					{
						$link = New-Object PSObject -Property @{
																ScopeOfManagement 		= $l.SelectSingleNode("./e:SOMName",$namespaceMgr).InnerXml
																ScopeOfManagementPath	= $l.SelectSingleNode("./e:SOMPath",$namespaceMgr).InnerXml
																Enabled								= $l.SelectSingleNode("./e:Enabled",$namespaceMgr).InnerXml.toLower() -eq "true"
																Enforced							= $l.SelectSingleNode("./e:NoOverride",$namespaceMgr).InnerXml.toLower() -eq "true"
						}
						$link.PSObject.TypeNames.Insert(0,"$($link.Psobject.TypeNames[0])#CGPOLinkDetail")
						$arrLinks += $link
					}
					Write-Output $arrLinks
				}
			}
		$GPODetails.PSObject.TypeNames.Insert(0,"$($GPODetails.Psobject.TypeNames[0])#CGPODetail")
		if ($IncludedExtensionDetails -ne "None")
		{
			foreach ($ext in $IncludedExtensionDetails)
			{
				switch ($ext)
				{
					# Depending on the extension detail requested we get the extension overview info
					# and then pipe the output to the correct detail gatherer for the extension
					# Piped to Out-Null at the end to keep the appending "silent"
					"Software Installation"
					{
						Add-CGPOExtensionDetail -gpoReports $GPODetails -namespaceMgr $namespaceMgr -extensionName $ext | Add-CGPOSoftwareInstallationDetail | Out-Null
						break
					}
					"Scripts"
					{
						Add-CGPOExtensionDetail -gpoReports $GPODetails -namespaceMgr $namespaceMgr -extensionName $ext | Add-CGPOScriptDetail | Out-Null
						break
					}
					"Registry"
					{
						Add-CGPOExtensionDetail -gpoReports $GPODetails -namespaceMgr $namespaceMgr -extensionName $ext | Add-CGPORegistryDetail | Out-Null
						break
					}
					"All"
					{
						#DONE: Implement 'All' Extension Detail in Get-CGPOReport
						# Gather the ExtensionDetail for all extensions present in the GPO
						$extensions = Add-CGPOExtensionDetail -gpoReports $GPODetails -namespaceMgr $namespaceMgr
						foreach ($e in $extensions)
						{
							# Choose which Extension detail filter to use to append detail with by the extension Name
							switch ( $e.extensionName )
							{
								"Software Installation" { $e | Add-CGPOSoftwareInstallationDetail | Out-Null }
								"Scripts"								{ $e | Add-CGPOScriptDetail | Out-Null }
								"Registry"							{ $e | Add-CGPORegistryDetail | Out-Null }
								default									{ } #No additional Details implemented
							}
						}
						# Clear out the 'temp' variable created by the foreach loop
						# How to make the 'temp' variable local to the foreach loop?  Question posted to newsgroup.
						if ($e) { Remove-Variable e }
						break
					}
					default
					{
						Add-CGPOExtensionDetail -gpoReports $GPODetails -namespaceMgr $namespaceMgr -extensionName $ext | Out-Null
						break
					}
				}
			}
		}
		if ($summarize)
		{
			#Summarize implementation goes here
		}
		else
		{
			if (($IncludedExtensionDetails -eq "None")`
					-or ($gpoDetails.extensionDetails -ne $null)`
					-or ($IncludeReportsWithNoExtensionDetails -eq $true))
			{
				Write-Output $GPODetails
			}
		}
	}
}
#endregion

filter Add-CGPOExtensionDetail
#region Add-CGPOExtensionDetail
{
#region Help
<#
  .SYNOPSIS
    Queries XML Reports generated by Get-GPOReport for specific Extension information
  .DESCRIPTION
    Finds the extension specified by the parameter ExtensionName in the
    Report or Reports (i.e. Files, Registry, Software Installation).  Tacks on
    the namespace information necessary to query the extension onto the report
    as a custom PSObject and writes that information to output.
  .PARAMETER gpoReport
    A report generated by Get-GPOReport -reportType XML.
  .PARAMETER reportPath
  	A path to either a single XML file or a directory that contains multiple XML files
  	that have been built and exported from Get-GPOReport -ReportType XML
  .PARAMETER namespaceMgr
  	If no namespace manager is assigned to this value, the default
  	namespaces for group policy XML reports are used
  .PARAMETER extensionName
  	The name of the group policy extension that you wish to find in the reports
  	If none is specified then all extensions will be written to output

  	Valid names I am currently aware of:
  		Security, Public Key, Registry, Remote Installation, Internet Explorer Maintenance,
  		Software Installation, Scripts, Folder Redirection, Printers, Windows Firewall,
  		Software Restriction, Drive Maps, Shortcuts, Folders, Files, Windows Registry,
  		Environment Variables, WLanSvc Networks, Folder Options, Start Menu,
  		Deployed Printer Connections Policy, Ini Files
  .EXAMPLE
    Get-GPOReport -All -ReportType XML | Get-CGPOExtension -ExtensionName "Drive Maps"
  .INPUTS
    [System.XML.XMLDocument]
    [System.String]
  .OUTPUTS
    [System.Management.Automation.PSCustomObject#CGPOExtensionInformation]
  .NOTES
    Todo.
  .FUNCTIONALITY
    Todo.
#>
#endregion
  #region cmdletbinding
  [CmdletBinding()]
  #endregion
  #region parameters
  param
  (
    [parameter(
      Mandatory=$false,
      ValueFromPipeline=$true
      )]
		[System.Management.Automation.PSObject[]]
    $gpoReports,
    [parameter(
      Mandatory=$false
      )]
    [Xml.XmlNamespaceManager]
    $namespaceMgr,
    [parameter(
      Mandatory=$false
      )]
    [String]
    $extensionName
  )
  #endregion
  process
  {
  	foreach ($g in $gpoReports)
  	{
	  	# If the ExtensionDetail member is not already present
	  	# Add an array to the cgpoReport to store the extension details objects
	  	if ((Get-Member -InputObject $g -Name ExtensionDetails) -eq $null)
	  	{
	  		Add-Member -InputObject $g -MemberType NoteProperty -Name ExtensionDetails -Value @()
	  	}
	  	# Get the XMLNode for the GPOReport
	  	$gpoReport = $g.fullReport
	  	# Use the namespace manager provided
	  	$namespaceMgr = $g.namespaceMgr
			if ($extensionName -ne '')
			{
		    # Gather the Extensions that match the our queryCSE
		    $extensions = $gpoReport.selectnodes("/e:GPO//e:ExtensionData[e:Name = '$extensionName']/e:Extension", $namespaceMgr)
	    }
	    else
	    {
	    	# Gather all extensions in the GPOReports
	    	$extensions = $gpoReport.selectnodes("/e:GPO//e:ExtensionData/e:Extension", $namespaceMgr)
	    }
	    foreach ($extension in $extensions)
	    {
				# The URI suffix for namespaces strips any whitespace separating words in the extension name
				# so we'll do the same, with a few exceptions...so we'll use switch to take care of that
				$extensionName = $extension.SelectSingleNode("./parent::e:ExtensionData/e:Name",$namespaceMgr).InnerText
				switch ($extensionName)
				{
					"Deployed Printer Connections Policy"
					{
						$extensionURISuffix = "PrinterConnections"
						break
					}
					"Environment Variables"
					{
						$extensionURISuffix = "Environment"
						break
					}
					"Internet Explorer Maintenance"
					{
						$extensionURISuffix = "IE"
						break
					}
					"Windows Registry"
					{
						$extensionURISuffix = "Windows/Registry"
						break
					}
					"WLanSvc Networks"
					{
						$extensionURISuffix = "WLanSvc"
						break
					}
					default
					{
	    			$extensionURISuffix =  $_ -replace " "
	    			break
	    		}
	    	}
	      # Get the extension specific namespace name
	      $extensionNamespaceURI  = "http://www.microsoft.com/GroupPolicy/Settings/$extensionURISuffix"
	      $extensionNamespacePrefix = $extension.GetPrefixOfNamespace($extensionNamespaceURI)
	      # Create a navigator to gather some additional information
	      $extensionNavigator = $extension.CreateNavigator()
	      $extensionGPOSection = $extensionNavigator.Evaluate("local-name(./parent::node()/parent::node())", $namespaceMgr)
			  # Now we have the namespace assignment that is valid to query this element
			  # So we'll create a custom object with that information attached and pass it
			  # along with the entirety of the report as well
			  $namespaceMgr.AddNamespace( $extensionNamespacePrefix, $extensionNamespaceURI )
		 	  $gpoExtInfo = New-Object PSObject -Property @{
												extensionGPOSection       = $extensionGPOSection
												extensionData             = $extension
												extensionName             = $extensionName
												extensionNamespacePrefix  = $extensionNamespacePrefix
												extensionNamespaceURI     = $extensionNamespaceURI
												namespaceMgr						  = $namespaceMgr
											}
				# Define the TypeName for our Custom Object
				# Take the current type and append CGPOExtensionInformation
				$gpoExtInfo.PSObject.TypeNames.Insert(0,"$($gpoExtInfo.Psobject.TypeNames[0])#CGPOExtensionDetail")
	      $g.ExtensionDetails += $gpoExtInfo
				Write-Output $gpoExtInfo
			} #end foreach extension			
			if ($extension) { Remove-Variable extension }
		} #end foreach g
		if ($g) { Remove-Variable g }
	} #end process
} #end function
#endregion

filter Add-CGPOSoftwareInstallationDetail
#region Add-CGPOSoftwareInstallationDetail
{
	#region Help
	<#  
	  .SYNOPSIS
	    Generates a CustomPSObject that contains the details for a Software Installation
	    group policy extension.
	  .DESCRIPTION
	    Generates a CustomPSObject that contains the details for the Software Installation
	    group policy extension.  If no extensionData object is specified then the details
	    for all software installation extensions in all current group policy object is
	    returned.
	  .PARAMETER  CGPOExtensionData
	    An object output from Get-CGPOExtension
	  .PARAMETER XPath
	  	Not currently implemented...may never be.
	  .EXAMPLE
	    Get-CGPOExtension -reportPath c:\temp\gp1.xml -extension "Software Installation" | Get-CGPOSoftwareInstallation
	  .INPUTS
	    [System.Management.Automation.PSCustomObject#CGPOExtensionInformation]
	  .OUTPUTS
	    [System.Management.Automation.PSCustomObject#CGPOMsiDetail]
	  .LINK
	    Get-CGPOExtension
	#>
	#endregion
  #region cmdletbinding
  [CmdletBinding()]
  #endregion
	#region parameters
	param
	(
    [parameter(
      Mandatory=$true,
      ValueFromPipeline=$true
      )]
    [System.Management.Automation.PSObject[]]
    $CGPOExtensionData
	)
	#endregion
	foreach ($e in $CGPOExtensionData)
	{
		# Use the namespace manager provided
		$namespaceMgr = $e.namespaceMgr

		# Define our namespace prefix for the Extension
		$ns = $e.extensionNamespacePrefix

		Add-Member -InputObject $e -MemberType NoteProperty -Name msiDetails -Value @()
		
		# Obtain the child nodeset 
		$msiNodes = $e.extensionData.SelectNodes("./$ns`:MsiApplication",$namespaceMgr)
		
		foreach ($m in $msiNodes)
		{
			$msiDetail = New-Object PSOBject -Property @{
				Identifier 						= $m.SelectSingleNode("./$ns`:Identifier",$namespaceMgr).InnerText
				Name 									= $m.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerText
				Path 									= $m.SelectSingleNode("./$ns`:Path",$namespaceMgr).InnerText
				MajorVersion 					= $m.SelectSingleNode("./$ns`:MajorVersion",$namespaceMgr).InnerText
				MinorVersion 					= $m.SelectSingleNode("./$ns`:MinorVersion",$namespaceMgr).InnerText
				LanguageId 						= $m.SelectSingleNode("./$ns`:LanguageId",$namespaceMgr).InnerText
				Architecture 					= $m.SelectSingleNode("./$ns`:Architecture",$namespaceMgr).InnerText
				IgnoreLanguage			 	= $m.SelectSingleNode("./$ns`:IgnoreLanguage",$namespaceMgr).InnerText
				Allowx86Onia64 				= $m.SelectSingleNode("./$ns`:Allowx86Onia64",$namespaceMgr).InnerText
				SupportURL 						= $m.SelectSingleNode("./$ns`:SupportURL",$namespaceMgr).InnerText
				AutoInstall 					= $m.SelectSingleNode("./$ns`:AutoInstall",$namespaceMgr).InnerText
				DisplayInARP 					= $m.SelectSingleNode("./$ns`:DisplayInARP",$namespaceMgr).InnerText
				IncludeCOM 						= $m.SelectSingleNode("./$ns`:IncludeCOM",$namespaceMgr).InnerText
				SecurityDescriptor 		= Get-CGPOSecurityDescriptor -sdNode $m.SelectSingleNode("./$ns`:SecurityDescriptor",$namespaceMgr) -namespaceMgr $namespaceMgr
				DeploymentType 				= $m.SelectSingleNode("./$ns`:DeploymentType",$namespaceMgr).InnerText
				ProductId 						= $m.SelectSingleNode("./$ns`:ProductId",$namespaceMgr).InnerText
				ScriptPath 						= $m.SelectSingleNode("./$ns`:ScriptPath",$namespaceMgr).InnerText
				DeploymentCount 			= $m.SelectSingleNode("./$ns`:DeploymentCount",$namespaceMgr).InnerText
				InstallationUILevel 	= $m.SelectSingleNode("./$ns`:InstallationUILevel",$namespaceMgr).InnerText
				UpgradesMandatory		= $m.SelectSingleNode("./$ns`:Upgrades/$ns`:Mandatory",$namespaceMgr).InnerText
				Upgrades							= &{
																	[PSObject[]]$arrUpgrades = @()
																	foreach ($p in $m.SelectNodes("./$ns`:Upgrades/$ns`:PackageInfo",$namespaceMgr))
																	{
																		$arrUpgrades += New-Object PSObject -Property @{
																												GPOIdentifier = $p.SelectSingleNode("./$ns`:PackageInfo/$ns`:GPOIdentifier/id:Identifier", $namespaceMgr).InnerXml
																												GPODomain			= $p.SelectSingleNode("./$ns`:PackageInfo/$ns`:GPOIdentifier/id:Domain", $namespaceMgr).InnerXml
																												GPOName				= $p.SelectSingleNode("./$ns`:GPOName",$namespaceMgr).InnerText
																												Name					= $p.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerText
																												Identifier		= $p.SelectSingleNode("./$ns`:Identifier",$namespaceMgr).InnerText
																		}
																	}
																	if ($p) { Remove-Variable p }
																	Write-Output $arrUpgrades
																}
				IsUpgradedBy					= &{
																	[PSObject[]]$arrUpgradedBy = @()
																	foreach ($p in $m.SelectNodes("./$ns`:IsUpgradedBy/$ns`:PackageInfo",$namespaceMgr))
																	{
																		$arrUpgradedBy += New-Object PSObject -Property @{
																												Name				= $p.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerText
																												Identifier	= $p.SelectSingleNode("./$ns`:Identifier",$namespaceMgr).InnerText
																		}
																	}
																	if ($p) { Remove-Variable p }
																	Write-Output $arrUpgradedBy
																}
				UninstallUnmanaged 		= $m.SelectSingleNode("./$ns`:UninstallUnmanaged",$namespaceMgr).InnerText
				LossOfScopeAction 		= $m.SelectSingleNode("./$ns`:LossOfScopeAction",$namespaceMgr).InnerText
				GPOName				    		= $e.GPOName
				fullReport			    	= $e.fullReport
				extensionData         = $e.extensionData
				extensionNamespacePrefix  = $e.extensionNamespacePrefix
				extensionNamespaceURI   = $e.extensionNamespaceURI
			}
			# Define the TypeName for our Custom Object
			# Take the current type and append CGPOMsiDetail
			$msiDetail.PSObject.TypeNames.Insert(0,"$($msiDetail.Psobject.TypeNames[0])#CGPOMsiDetail")
			$e.msiDetails += $msiDetail
		} #end foreach m
		if ($m) { Remove-Variable m }
		Write-Output $e
	} #end foreach e
	if ($e) { Remove-Variable e }
}
#endregion

filter Add-CGPOScriptDetail
#region Add-CGPOScriptDetail
{
	#region Help
	<#  
	  .SYNOPSIS
	    Generates a CustomPSObject that contains the details for a Script
	    group policy extension.
	  .DESCRIPTION
	    Generates a CustomPSObject that contains the details for a Script
	    group policy extension.
	  .PARAMETER  CGPOExtensionData
	    An object output from Get-CGPOExtension
	  .EXAMPLE
	    Get-CGPOExtension -reportPath c:\temp\gp1.xml -extension "Scripts" | Add-CGPOScriptDetail
	  .INPUTS
	    [System.Management.Automation.PSCustomObject#CGPOExtensionInformation]
	  .OUTPUTS
	    [System.Management.Automation.PSCustomObject#CGPOExtensionInformation]
	  .LINK
	    Get-CGPOExtension
	#>
	#endregion
  #region cmdletbinding
  [CmdletBinding()]
  #endregion
	#region parameterSetDefault
	param
	(
    [parameter(
      Mandatory=$true,
      ValueFromPipeline=$true
      )]
    [System.Management.Automation.PSObject[]]
    $CGPOExtensionData
	)
	#endregion
	foreach ($e in $CGPOExtensionData)
	{
		# Use the namespace manager provided
		$namespaceMgr = $e.namespaceMgr

		# Define our namespace prefix for the Extension
		$ns = $e.extensionNamespacePrefix
		
		# Obtain the child nodeset 
		$scriptNodes = $e.extensionData.SelectNodes("./$ns`:Script",$namespaceMgr)
		# Add an array to hold the script detail objects that we will append to the
		# ExtensionInformation object
		Add-Member -InputObject $e -MemberType NoteProperty -Name ScriptDetails -Value @()

		foreach ($s in $scriptNodes)
		{
			$scriptDetail = New-Object PSOBject -Property @{
				Command			= $s.SelectSingleNode("./$ns`:Command",$namespaceMgr).InnerXml
				Parameters	= $s.SelectSingleNode("./$ns`:Parameters",$namespaceMgr).InnerXml
				ScriptType	= $s.SelectSingleNode("./$ns`:Type",$namespaceMgr).InnerXml
				Order				= $s.SelectSingleNode("./$ns`:Order",$namespaceMgr).InnerXml
				RunOrder		= $s.SelectSingleNode("./$ns`:RunOrder",$namespaceMgr).InnerXml
				}
			$scriptDetail.PSObject.TypeNames.Insert(0,"$($scriptDetail.Psobject.TypeNames[0])#CGPOScriptDetail")
			$e.scriptDetails += $scriptDetail
		}
		if ($s) { Remove-Variable s }
		Write-Output $e
	}
	if ($e) { Remove-Variable e }
}
#endregion

filter Add-CGPORegistryDetail
#region Add-CGPORegistryDetail
{
	param
	(
    [parameter(
      Mandatory=$true,
      ValueFromPipeline=$true
      )]
    [System.Management.Automation.PSObject[]]
    $CGPOExtensionData
	)
	foreach ($e in $CGPOExtensionData)
	{
		# Use the namespace manager provided
		$namespaceMgr = $e.namespaceMgr
		# Define our namespace prefix for the Extension
		$ns = $e.extensionNamespacePrefix
		
		# Add an array to hold the Policy detail objects that we will append to the
		# ExtensionInformation object
		Add-Member -InputObject $e -MemberType NoteProperty -Name PolicyDetails -Value @()
		# Obtain the child nodeset 
		$PolicyNodes = $e.extensionData.SelectNodes("./$ns`:Policy",$namespaceMgr)
		foreach ($p in $PolicyNodes)
		{
			$PolicyDetail = New-Object PSOBject -Property @{
				name				= $p.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerXml
				state				= $p.SelectSingleNode("./$ns`:State",$namespaceMgr).InnerXml
				explanation	=	$p.SelectSingleNode("./$ns`:Explain",$namespaceMgr).InnerXml
				supported		= $p.SelectSingleNode("./$ns`:Supported",$namespaceMgr).InnerXml
				category		= $p.SelectSingleNode("./$ns`:Category",$namespaceMgr).InnerXml
				options		= &{
					[PSObject[]]$arrOptions = @()
					foreach ($x in $p.SelectNodes("./child::node()[local-name() = 'Numeric' or local-name() = 'Text' or local-name() = 'CheckBox' or local-name() = 'ListBox']",$namespaceMgr))
					{
						switch ($x.LocalName)
						{
							"Numeric"
							{
								$arrOptions += New-Object PSObject -Property @{
									optionType	= "Numeric"
									name				= $x.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerXml
									state				= $x.SelectSingleNode("./$ns`:State",$namespaceMgr).InnerXml
									value				= $x.SelectSingleNode("./$ns`:Value",$namespaceMgr).InnerXml
								}
								break
							}
							"Text"
							{
								$arrOptions += New-Object PSObject -Property @{
									optionType	= "Text"
									name				= $x.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerXml
								}
								break
							}
							"CheckBox"
							{
								$arrOptions += New-Object PSObject -Property @{
									optionType	= "CheckBox"
									name				= $x.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerXml
									state				= $x.SelectSingleNode("./$ns`:State",$namespaceMgr).InnerXml
								}
								break
							}
							"ListBox"
							{
								$arrOptions += New-Object PSOBject -Property @{
									optionType		= "ListBox"
									Name					= $x.SelectSingleNode("./$ns`:Name",$namespaceMgr).InnerXml
									State					= $x.SelectSingleNode("./$ns`:State",$namespaceMgr).InnerXml
									ExplicitValue	= $x.SelectSingleNode("./$ns`:ExplicitValue",$namespaceMgr).InnerXml #No idea what this corresponds to...
									Additive			= $x.SelectSingleNode("./$ns`:Additive",$namespaceMgr).InnerXml.toLower() -eq "true"
									Values				= $x.SelectNodes("$ns`:Value//$ns`:Data/text()",$namespaceMgr) | ForEach-Object { $_.value }
								}
								break
							}
						}
					} #end foreach x
					if ($x) { Remove-Variable x }
					Write-Output $arrOptions
				}
			}
			$PolicyDetail.PSObject.TypeNames.Insert(0,"$($PolicyDetail.Psobject.TypeNames[0])#CGPOPolicyDetail")
			$e.PolicyDetails += $PolicyDetail
		} #end foreach p
		if ($p) { Remove-Variable p }
		# Add an array to hold the ExtraRegistry detail objects that we will append to the
		# ExtensionInformation object
		Add-Member -InputObject $e -MemberType NoteProperty -Name ExtraRegistryDetails -Value @()
		# Obtain the child nodeset 
		$ExtraRegistryNodes = $e.extensionData.SelectNodes("./$ns`:RegistrySetting",$namespaceMgr)
		foreach ($r in $ExtraRegistryNodes)
		{
			$ExtraRegistryDetail = New-Object PSOBject -Property @{
				Key					= $r.SelectSingleNode("./$ns`:KeyPath",$namespaceMgr).InnerXml
				AdmSetting	= $r.SelectSingleNode("./$ns`:AdmSetting",$namespaceMgr).InnerXml.tolower() -eq "true"
				ValueName		= $r.SelectSingleNode("./$ns`:Value/$ns`:Name",$namespaceMgr).InnerXml
				Value				= $r.SelectSingleNode("./$ns`:Value/child::node()[local-name() != 'Name' and ./child::node()]",$namespaceMgr).InnerXml
			}
			$ExtraRegistryDetail.PSObject.TypeNames.Insert(0,"$($ExtraRegistryDetail.Psobject.TypeNames[0])#CGPOExtraRegistryDetail")
			$e.ExtraRegistryDetails += $ExtraRegistryDetail
		} #end foreach r
		if ($r) { Remove-Variable r }
	} #end foreach e
	if ($e) { Remove-Variable e }
}
#endregion

filter Get-CGPOSecurityDescriptor
#region Get-CGPOSecurityDescriptor
{
	param
	(
		[parameter(
			Mandatory=$true
			#HelpMessage="Help goes here"
		)]
		[ValidateNotNullOrEmpty()]
		[System.XML.XmlElement]
		$sdNode,
    [parameter(
      Mandatory=$true
      )]
    [Xml.XmlNamespaceManager]
    $namespaceMgr
	)
	$sd = New-Object PSObject -Property @{
		SDDL = $sdNode.SelectSingleNode( "./sd:SDDL", $namespaceMgr ).InnerXml
		OwnerName	= $sdNode.SelectSingleNode( "./sd:Owner/id:Name", $namespaceMgr).InnerXml
		OwnerSID	= $sdNode.SelectSingleNode( "./sd:Owner/id:SID", $namespaceMgr).InnerXml
		GroupName	= $sdNode.SelectSingleNode( "./sd:Group/id:Name", $namespaceMgr).InnerXml
		GroupSID	= $sdNode.SelectSingleNode( "./sd:Group/id:SID", $namespaceMgr).InnerXml
		PermissionsPresent = $sdNode.SelectSingleNode( "./sd:PermissionsPresent", $namespaceMgr ).InnerXml.ToLower() -eq "true"
		AuditingPresent = $sdNode.SelectSingleNode("./sd:AuditingPresent",$namespaceMgr).InnerXml.ToLower() -eq "true"
	}
	if ($sd.PermissionsPresent)
	{
		Add-Member	-InputObject $sd `
								-MemberType NoteProperty `
								-Name PermissionsInheritFromParent `
								-Value	(& { $sdNode.SelectSingleNode( "./sd:Permissions/sd:InheritsFromParent", $namespaceMgr).InnerXml.ToLower() -eq "true" })
		Add-Member	-InputObject $sd `
								-MemberType NoteProperty `
								-Name TrusteePermissions `
								-Value	(& {
													[PSObject[]]$arrTPerms = @()
													foreach ($p in $sdNode.SelectNodes("./sd:Permissions/sd:TrusteePermissions",$namespaceMgr))
													{
														$tPermission = New-Object PSObject -Property @{
															TrusteeName 		= $p.SelectSingleNode("./sd:Trustee/id:Name",$namespaceMgr).InnerXml
															TrusteeSID			= $p.SelectSingleNode("./sd:Trustee/id:SID",$namespaceMgr).InnerXml
															PermissionType	= $p.SelectSingleNode("./sd:Type/sd:PermissionType",$namespaceMgr).InnerXml
															Inherited				= $p.SelectSingleNode("./sd:Inherited",$namespaceMgr).InnerXml
															ApplicableToSelf									= $p.SelectSingleNode("./sd:Applicability/sd:ToSelf",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDescendentObjects 		= $p.SelectSingleNode("./sd:Applicability/sd:ToDescendantObjects",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDescendentContainers	= $p.SelectSingleNode("./sd:Applicability/sd:ToDescendantContainers",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDirectDescendentsOnly = $p.SelectSingleNode("./sd:Applicability/sd:ToDirectDescendantsOnly",$namespaceMgr).InnerXml.ToLower() -eq "true"
															Standards		= & {
																$arrStds = @()
																foreach ($s in $p.SelectNodes("./sd:Standard/*",$namespaceMgr))
																{
																	$std = New-Object PSObject -Property @{
																		Standard			= $s.SelectSingleNode(".",$namespaceMgr).InnerXml
																		StandardType	= $s.CreateNavigator().Evaluate("local-name(.)",$namespaceMgr)
																	}
																	$arrStds += $std
																}
																if ($s) { Remove-Variable s }
																Write-Output $arrStds
															}
															AccessMask	= $p.SelectSingleNode("./sd:AccessMask",$namespaceMgr).InnerXml
														}
														$tPermission.PSObject.TypeNames.Insert(0,"$($tPermission.Psobject.TypeNames[0])#CGPOTrusteePermissionDetail")
														$arrTPerms += $tPermission
													}
													if ($p) { Remove-Variable p }
													Write-Output $arrTPerms
												})
	}
	if ($sd.AuditingPresent)
	{
		Add-Member	-InputObject $sd `
								-MemberType NoteProperty `
								-Name AuditingInheritsFromParent `
								-Value (&{ $sdNode.SelectSingleNode("./sd:Auditing/sd:InheritsFromParent",$namespaceMgr).InnerXml.ToLower() -eq "true" })
		Add-Member	-InputObject $sd `
								-MemberType NoteProperty `
								-Name TrusteeAuditing `
								-Value	(&{
													[PSObject[]]$arrTAudits = @()
													foreach ($a in $sdNode.SelectNodes("./sd:Auditing/sd:TrusteeAuditing",$namespaceMgr))
													{
														$tAudit = New-Object PSObject -Property @{
															TrusteeName 		= $a.SelectSingleNode("./sd:Trustee/id:Name",$namespaceMgr).InnerXml
															TrusteeSID			= $a.SelectSingleNode("./sd:Trustee/id:SID",$namespaceMgr).InnerXml
															AuditType				= $a.SelectSingleNode("./sd:Type/sd:AuditType",$namespaceMgr).InnerXml
															Inherited				= $a.SelectSingleNode("./sd:Inherited",$namespaceMgr).InnerXml
															ApplicableToSelf									= $a.SelectSingleNode("./sd:Applicability/sd:ToSelf",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDescendentObjects 		= $a.SelectSingleNode("./sd:Applicability/sd:ToDescendantObjects",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDescendentContainers	= $a.SelectSingleNode("./sd:Applicability/sd:ToDescendantContainers",$namespaceMgr).InnerXml.ToLower() -eq "true"
															ApplicableToDirectDescendentsOnly = $a.SelectSingleNode("./sd:Applicability/sd:ToDirectDescendantsOnly",$namespaceMgr).InnerXml.ToLower() -eq "true"
															Standards		= & {
																$arrStds = @()
																foreach ($s in $a.SelectNodes("./sd:Standard/*",$namespaceMgr))
																{
																	$std = New-Object PSObject -Property @{
																		Standard			= $s.SelectSingleNode(".",$namespaceMgr).InnerXml
																		StandardType	= $s.CreateNavigator().Evaluate("local-name(.)",$namespaceMgr)
																	}
																	$arrStds += $std
																}
																if ($s) { Remove-Variable s }
																Write-Output $arrStds
															}
															AccessMask			= $a.SelectSingleNode("./sd:AccessMask",$namespaceMgr).InnerXml
														}
														$tAudit.PSObject.TypeNames.Insert(0,"$($tAudit.Psobject.TypeNames[0])#CGPOTrusteeAuditDetail")
														$arrTAudits += $tAudit
													}
													if ($a) { Remove-Variable a }
													Write-Output $arrTAudits
												})
	}
	$sd.PSObject.TypeNames.Insert(0,"$($sd.Psobject.TypeNames[0])#CGPOSecurityDescriptorDetail")
	Write-Output $sd
}
#endregion

function Search-CGPOReports
{
	[CmdletBinding(
		DefaultParameterSetName="gpoReports"
		)]
	param
	(
		[parameter(
			Mandatory=$false,
			Position=2,
			ParameterSetName="cgpoReports",
			ValueFromPipeline=$true
			#HelpMessage="Help goes here"
		)]
		[ValidateNotNullOrEmpty()]
		[System.Management.automation.PSObject[]]
		$cgpoReports,
		[parameter(
			Mandatory=$false,
			Position=2,
			ParameterSetName="gpoReports",
			ValueFromPipeline=$false
			#HelpMessage="Help goes here"
		)]
		[ValidateScript({ test-path $_ })]
		[ValidateNotNull()]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$gpoReportPath = "$global:CGPO_ReportXmlCachePath",
		[parameter(
			Mandatory=$false,
			Position=1,
			ValueFromPipeline=$false,
			HelpMessage="A comma separated list of extensions to search, e.g. 'Registry, Scripts'"
		)]
		#[ValidateSet("One","Two","Three")]
		[System.Object]
		$extensionsToSearch = "All",
		[parameter(
			Mandatory=$true,
			Position=0,
			ValueFromPipeline=$false
			#HelpMessage="Help goes here"
		)]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$pattern,
		[parameter(
			Mandatory=$false,
			ParameterSetName="gpoReports"
			#HelpMessage="Help goes here"
		)]
		[switch]
		$live,
		[parameter(
			Mandatory=$false
			#HelpMessage="Help goes here"
			)]
		[switch]
		$summarize
	)
	begin
	{
		# If we're not using output from get-cgporeports we'll have to generate
		# the cgpoReports
		if ($pscmdlet.parametersetname -ne "cgpoReports")
		{
			# refresh the cache if the live switch is present or if the cache does not exist
			if ($live -or ( -not (Test-Path $global:CGPO_ReportXmlCachePath))) { Update-GPOReportCache }
			# Obtain the output from Get-CGPOReport
			$cgpoReports = Get-CGPOReport -reportPath $gpoReportPath -IncludedExtensionDetails $extensionsToSearch
		}
		# Setup the output object(s), a separate array with type name info for each extension type makes displaying
		# the results at the end of the search much easier
		$softwareHits = @()
		$scriptHits = @()
		$policyHits = @()
		$extraRegHits = @()
	}
	process
	{
		foreach ($c in $cgpoReports)
		{
			# Assume that the match is false
			$matchFound = $false
			# Get the extensionDetails member(s) that match our extensionsToSearch, which is all of them by default
			if ($extensionsToSearch -eq "All")
			{
				$extsToSearch = $c.extensionDetails
			}
			else
			{
				$extsToSearch = $c.extensionDetails | Where-Object { $extensionsToSearch -contains $_.extensionName }
			}
			foreach ($e in $extsToSearch)
			{
				# Setup the namespacemanager for the extension
				$namespaceMgr = $e.namespaceMgr
				# What and how we search depends on the type of extension
				# This will expand as we implement more extension types into the module
				switch ($e.extensionName)
				{
					"Software Installation"
					{
						#DONE: Implement Software Installation search
						if ($e.msidetails.count -gt 0)
						{
							foreach ($m in $e.msidetails)
							{
								if ($m.Identifier -match $pattern) { $matchFound = $true }
								if ($m.SupportURL -match $pattern) { $matchFound = $true }
								if ($m.ScriptPath -match $pattern) { $matchFound = $true }
								if ($m.ProductId -match $pattern) { $matchFound = $true }
								if ($m.Name -match $pattern) { $matchFound = $true }
								if ($m.Path -match $pattern) { $matchFound = $true }
								# If we have found a match in the MSI setting add the MSI object to the output array
								if ($matchFound)
								{
									# Construct a Software Installation search hit PSObject
									$softwareSearchHit = New-Object PSOBject -Property @{
										HitType					= "Software"
										Name						= $m.Name
										MajorVersion		= $m.MajorVersion
										MinorVersion		= $m.MinorVersion
										ProductId				= $m.ProductId
										SupportURL			= $m.SupportURL
										ScriptPath			= $m.ScriptPath
										Path						= $m.Path
										GPOName					= $c.Name
										GPOReportPath		= $c.reportPath
									}
									$softwareSearchHit.PSObject.TypeNames.Insert(0,"$($softwareSearchHit.Psobject.TypeNames[0])#CGPOSoftwareSearchHit")
									$softwareHits += $softwareSearchHit
									# Reset the matchFound flag
									$matchFound = $false
								}
							}
							if ($m) { Remove-Variable m } #foreach cleanup
						}
						break
					}
					"Scripts"
					{
						#DONE: Implement Script search
						if ($e.ScriptDetails.count -gt 0)
						{
							foreach ($s in $e.ScriptDetails)
							{
								if ($s.ScriptType -match $pattern) { $matchFound = $true }
								if ($s.Command -match $pattern) { $matchFound = $true }
								if ($s.Parameters -match $pattern) { $matchFound = $true }
								# If we have found a match in the registry setting add the registry object to the output array
								if ($matchFound)
								{
									# Construct a script search hit PSObject
									$scriptSearchHit = New-Object PSOBject -Property @{
										HitType					= "Script"
										ScriptType		= $s.ScriptType
										Command				= $s.Command
										Parameters		= $s.Parameters
										GPOName				= $c.Name
										GPOReportPath	= $c.reportPath
									}
									$scriptSearchHit.PSObject.TypeNames.Insert(0,"$($scriptSearchHit.Psobject.TypeNames[0])#CGPOScriptSearchHit")
									$scriptHits += $scriptSearchHit
									# Reset the matchFound flag
									$matchFound = $false
								}
							}
							if ($s) { Remove-Variable s } #foreach cleanup
						}
						break
					}
					"Registry"
					{
						# If there are Policy Details we'll search through the relevant portion of those
						if ($e.PolicyDetails.count -gt 0)
						{
							# For Policies we'll search the name, category, explanation and options
							foreach ($p in $e.PolicyDetails)
							{
								if ($p.name -match $pattern) {$matchFound = $true}
								if ($p.explanation -match $pattern) {$matchFound = $true}
								if ($p.category -match $pattern) {$matchFound = $true}
								foreach ($o in $p.options)
								{
									if ($o.name -match $pattern) {$matchFound = $true}
									if ($o.value -match $pattern) {$matchFound = $true}
									if ($o.values -match $pattern) {$matchFound = $true}
								}
								if ($o) { Remove-Variable o }
								# If we have found a match in the policy setting add the policy object to the output array
								if ($matchFound)
								{
									# Construct a policy search hit PSObject
									$policySearchHit = New-Object PSOBject -Property @{
										HitType				= "Policy"
										Name					= $p.name
										Explanation		= $p.explanation
										Category			=	$p.category
										Options				= $p.options
										GPOName				= $c.Name
										GPOReportPath	= $c.reportPath
									}
									$policySearchHit.PSObject.TypeNames.Insert(0,"$($policySearchHit.Psobject.TypeNames[0])#CGPOPolicySearchHit")
									$policyHits += $PolicySearchHit
									# Reset the matchFound flag
									$matchFound = $false
								}
							}
							if ($p) { Remove-Variable p }
						}
						# If there are Extra Registry Settings we'll search though those
						if ($e.ExtraRegistryDetails.count -gt 0)
						{
							foreach ($r in $e.ExtraRegistryDetails)
							{
								if ($r.key -match $pattern) {$matchFound = $true}
								if ($r.value -match $pattern) {$matchFound = $true}
								if ($r.valueName -match $pattern) {$matchFound = $true}
								# If we have found a match in the registry setting add the registry object to the output array
								if ($matchFound)
								{
									# Construct a policy search hit PSObject
									$extraRegSearchHit = New-Object PSOBject -Property @{
										HitType				= "ExtraReg"
										Key						= $r.key
										Value					= $r.value
										ValueName			=	$r.valueName
										GPOName				= $c.Name
										GPOReportPath	= $c.reportPath
									}
									$extraRegSearchHit.PSObject.TypeNames.Insert(0,"$($extraRegSearchHit.Psobject.TypeNames[0])#CGPOExtraRegSearchHit")
									$extraRegHits += $extraRegSearchHit
									# Reset the matchFound flag
									$matchFound = $false
								}
							}
							if ($r) { Remove-Variable r }
						}
						break
					}
				}
			}
			if ($e) { Remove-Variable e }
		}
	}
	end
	{
		#DONE: Format and make something useful to present from the result of searches
		# Add custom type info to the array objects
		$softwareHits.PSObject.TypeNames.Insert(0,"$($softwareHits.Psobject.TypeNames[0])#CGPOSoftwareHits")
		Add-Member -InputObject $softwareHits -Name TypeName -Value $softwareHits.PSObject.TypeNames[0] -MemberType NoteProperty
		$scriptHits.PSObject.TypeNames.Insert(0,"$($scriptHits.Psobject.TypeNames[0])#CGPOScriptHits")
		Add-Member -InputObject $scriptHits -Name TypeName -Value $scriptHits.PSObject.TypeNames[0] -MemberType NoteProperty
		$policyHits.PSObject.TypeNames.Insert(0,"$($policyHits.Psobject.TypeNames[0])#CGPOPolicyHits")
		Add-Member -InputObject $policyHits -Name TypeName -Value $policyHits.PSObject.TypeNames[0] -MemberType NoteProperty
		$extraRegHits.PSObject.TypeNames.Insert(0,"$($extraRegHits.Psobject.TypeNames[0])#CGPOExtraRegHits")
		Add-Member -InputObject $extraRegHits -Name TypeName -Value $extraRegHits.PSObject.TypeNames[0] -MemberType NoteProperty
		# It appears PowerShell cannot handle formatting multiple object types from output so in the meantime we'll
		# use prefomatted output if -summarize is specified
		if ($summarize)
		{
 	 		$softwareHits,$scriptHits,$policyHits,$extraRegHits | Group-Object -Property PSTypeNames | ForEach-Object { $_.group | Format-Table }
		}
		else
		{
 	 		Write-Output $softwareHits
 	 		Write-Output $scriptHits
 	 		Write-Output $policyHits
 	 		Write-Output $extraRegHits
		}
	}
}

function Update-GPOReportCache
{
	param
	(
		[parameter(
			Mandatory=$false,
			Position=1
			#HelpMessage="Help goes here"
		)]
		[System.String]
		$cachePath = $global:CGPO_ReportXmlCachePath, #$CGPO_ReportXmlCache is assigned a value during import using CGPO.GroupPolicy.Config.Xml
		[parameter(
			Mandatory=$false,
			Position=0
			#HelpMessage="Help goes here"
		)]
		[System.String]
		$domain = $global:CGPO_Domain #$CGPO_Domain is assigned a value during import using CGPO.GroupPolicy.Config.Xml
	)
	# If the cache directory does not exist create it
	if (-not (Test-Path $cachePath))
	{
		mkdir $cachePath | Out-Null
	}
	# Flush the cache
	Get-ChildItem -Path $cachePath -Include *,*.* -Recurse -Force | Remove-Item
	# Generate a new cache
	# DONE: Progress Bar for Updating the report cache
	Write-Progress -Activity "Getting GPO Reports from Active Directory" -Status "This may take some time..." -PercentComplete -1
	$gpoReports = Get-GPOReport -Domain $domain -All -ReportType XML
	for ( $i = 0 ; $i -lt $gpoReports.count ; $i++ )
	{
		Out-File -FilePath "$cachePath\$i.xml" -InputObject $gpoReports[$i]
		Write-Progress -Activity "Writing GPO Reports to $cachePath" -Status "% Complete" -PercentComplete ($i/$($gpoReports.count)*100)
	}
	Write-Progress -Activity "Writing GPO Reports to $cachePath" -Status "% Complete" -Completed
}

function Backup-GroupPolicyObjects
{
	param
	(
		# What domain are we going to backup GPOs for?
		$domain = "psd267.wednet.edu",
		# Where are we going to store the backups?
		$gpoBackupRootDir = "\\psd267.wednet.edu\backup\grouppolicy",
		# As I plan to do a new backup set each month I'll setup the directory names to reflect
		# the year and month in a nice sortable way.
		# Set this up and format to your liking, I prefer $gpoBackupRootDir\yyyy-MM
		$backupDir = "$gpoBackupRootDir\{0:yyyy-MM}" -f (Get-Date),
		
		# Perform a full backup how often? Day/Week/Month/Year?
		#$fullBackupFrequency = "Day",
		#$fullBackupFrequency = "Week",
		$fullBackupFrequency = "Month",
		#$fullBackupFrequency = "Year",
		
		# Perform Incremental backups how often?  Hour/Day/Week/Month?
		$IncBackupFreqency = "Hour",
		# $IncBackupFreqency = "Day",
		# $IncBackupFreqency = "Week",
		# $IncBackupFreqency = "Month",
		
		# How many full sets to keep?
		# Alternatively, how far back do we keep our backup sets?
		$numKeepBackupSets = 12,
		
		# On what day do we want to consider the start of Week?
		#$startOfWeek = "Sunday",
		$startOfWeek = "Monday",
		#$startOfWeek = "Tuesday",
		#$startOfWeek = "Wednesday",
		#$startOfWeek = "Thursday",
		#$startOfWeek = "Friday",
		#$startOfWeek = "Saturday",
		
		# On what day do we want to consider the start of Month?
		$startOfMonth = 1,
		
		# On what day do we want to consider the start of Year?
		$startOfYear = 1
	)
	
	$currentDateTime = Get-Date
	$doFull = $false
	$doInc  = $false
	
	# Does our backup directory exist?
	# If not attempt to create it and fail the script with an approprate error
	if (-not (Test-Path $backupDir))
	{
		try 
		{
			New-Item -ItemType Directory -Path $backupDir
		}
		catch
		{
			Throw $("Could not create directory $backupDir")
		}
	}
	
	# If we're here then our backup directory is in good shape
	# Check if we need to run a full backup or not
	#  if we do, then run it
	if ( Test-Path $backupDir\LastFullTimestamp.xml )
	{
		# Import the timestamp from the last recorded complete full
		$lastFullTimestamp = Import-Clixml $backupDir\LastFullTimestamp.xml
		# check to see if the timestamp is valid, if not then delete it and run a full
		if ( $lastFullTimestamp -isnot [datetime] )
		{
			$doFull = $true
			Remove-Item $backupDir\LastFullTimestamp.xml
		}
		else # $lastfulltimestamp is or can be boxed/cast into [datetime]
		{
			# determine how long it has been since the last recorded full
			$fullDelta = $currentDateTime - $lastFullTimestamp
			switch ($fullBackupFrequency)
			{
				Day
				{
					if ( $fullDelta.days -gt 0 )
					{
						$doFull = $true
					}
				}
				Week
				{
					if ( ($currentDateTime.dayOfWeek -eq [DayOfWeek]$startOfWeek) `
						 -or ($fullDelta.days -gt 7) )
					{
						$doFull = $true
					}
				}
				Month
				{
					if ( ($currentDateTime.day -eq $startOfMonth) `
						 -or ($fullDelta.days -gt 30) )
					{
						$doFull = $true
					}
				}
				Year
				{
					if ( ($currentDateTime.dayofyear -eq $startOfYear) `
						 -or ($fullDelta.days -gt 365) )
					{
						$doFull = $true
					}
				}
			}
		}
	}
	else # There is no recorded last completed full so we want to run one
	{
		$doFull = $true
	}
	
	if ($doFull)
	{
		# Run Backup of All GPOs in domain
		$GPOs = Get-GPO -domain $domain -All
		foreach ($GPO in $GPOs)
		{
			$GPOBackup = Backup-GPO $GPO.DisplayName -Path $backupDir
		    # First build the Report path, then generate a report of the backed up settings.
		    $HTMLReportPath = $backupDir + "\" + $GPO.ModificationTime.Year + "-" + $GPO.ModificationTime.Month + "-" + $GPO.ModificationTime.Day + "_" +  $GPO.Displayname + "_" + $GPOBackup.Id + ".html"
	 	    $XMLReportPath = $backupDir + "\" + $GPO.ModificationTime.Year + "-" + $GPO.ModificationTime.Month + "-" + $GPO.ModificationTime.Day + "_" +  $GPO.Displayname + "_" + $GPOBackup.Id + ".xml"
		    Get-GPOReport -Name $GPO.DisplayName -path $HTMLReportPath -ReportType HTML
		    Get-GPOReport -Name $GPO.DisplayName -path $XMLReportPath  -ReportType XML
		}
		Export-Clixml -Path $backupDir\LastFullTimestamp.xml -InputObject ($currentDateTime)
	}
	else # If we're not running a full check if we need to run an incremental backup
	{
		if ( Test-Path $backupDir\LastIncTimestamp.xml )
		{
			# Import the timestamp from the last recorded complete Incremental
			$lastIncTimestamp = Import-Clixml $backupDir\LastIncTimestamp.xml
			# check to see if the timestamp is valid, if not then delete it and run an inc
			if ( $lastIncTimestamp -isnot [datetime] )
			{
				# Import the timestamp from the last recorded complete full
				# If we're here then the timestamp is valid. It is checked earlier and if it fails
				# or doesn't exist then we run a full and will never get here.
				# determine how long it has been since the last recorded full
				$lastFullTimestamp = Import-Clixml $backupDir\LastFullTimestamp.xml
				$IncDelta = $currentDateTime - $lastFullTimestamp
				$doInc = $true
				Remove-Item $backupDir\LastIncTimestamp.xml
			}
			else # $lastIncTimestamp is or can be boxed/cast into [datetime]
			{
				# determine how long it has been since the last recorded full
				$IncDelta = $currentDateTime - $lastIncTimestamp
			}
		}
		else # There is no recorded last Incremental
		{
			# Import the timestamp from the last recorded complete full
			# If we're here then the timestamp is valid. It is checked earlier and if it fails
			# or doesn't exist then we run a full and will never get here.
			# determine how long it has been since the last recorded full
			$lastFullTimestamp = Import-Clixml $backupDir\LastFullTimestamp.xml
			$IncDelta = $currentDateTime - $lastFullTimestamp
		}
		# If we have already determined to run an Inc we want to skip this part
		if ($doInc -eq $false)
		{
			switch ($IncBackupFreqency)
			{
				Hour
				{
					if ($IncDelta.hours -gt 0)
					{
						$doInc = $true
					}
				}
				Day
				{
					if ($IncDelta.days -gt 0)
					{
						$doInc = $true
					}
				}
				Week
				{
					if ( ($currentDateTime.dayOfWeek -eq [DayOfWeek]$startOfWeek) `
						 -or ($IncDelta.days -gt 7) )
					{
						$doInc = $true
					}
				}
				Month
				{
					if ( ($currentDateTime.day -eq $startOfMonth) `
						 -or ($IncDelta.days -gt 30) )
					{
						$doInc = $true
					}
				}
			}
		}
		# Time to check our Incremental flag and run the backup if we need to
		if ($doInc)
		{
			# Run Incremental Backup
			$GPOs = Get-GPO -domain $domain -All | Where-Object { $_.modificationTime -gt ($currentDateTime - $incDelta) }
			foreach ($GPO in $GPOs)
			{
				$GPOBackup = Backup-GPO $GPO.DisplayName -Path $backupDir
			    # First build the Report path, then generate a report of the backed up settings.
			    $HTMLReportPath = $backupDir + "\" + $GPO.ModificationTime.Year + "-" + $GPO.ModificationTime.Month + "-" + $GPO.ModificationTime.Day + "_" +  $GPO.Displayname + "_" + $GPOBackup.Id + ".html"
		 	    $XMLReportPath = $backupDir + "\" + $GPO.ModificationTime.Year + "-" + $GPO.ModificationTime.Month + "-" + $GPO.ModificationTime.Day + "_" +  $GPO.Displayname + "_" + $GPOBackup.Id + ".xml"
			    Get-GPOReport -Name $GPO.DisplayName -path $HTMLReportPath -ReportType HTML
			    Get-GPOReport -Name $GPO.DisplayName -path $XMLReportPath  -ReportType XML
			}
			Export-Clixml -Path $backupDir\LastIncTimestamp.xml -InputObject ($currentDateTime)
		}
	}
	#TODO: Cleanup old backup sets
}

# Register the cleanup script for when the module is removed
$moduleInfo = $MyInvocation.MyCommand.ScriptBlock.Module
$moduleInfo.OnRemove = { & "$PSScriptRoot\onRemove.ps1" }
